package edu.caltech.android;

import java.util.List;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.Overlay;

import edu.caltech.android.c2dm.HelpActivity;
import edu.caltech.android.c2dm.Prefs;
import edu.caltech.android.c2dm.SetupActivity;
import edu.caltech.android.earthquake.Quake;
import edu.caltech.android.picking.Pick;
import edu.caltech.android.waveforms.WaveformDisplay;

/**
 * Displays a shake map.
 * 
 * Info on MapActivity:
 * http://code.google.com/intl/ja/android/add-ons/google-apis/maps-overview.html
 * 
 * how to draw things on the map. Related classes: MapOverlay, ItemizedOverlay
 * see http://eagle.phys.utk.edu/guidry/android/mapOverlayDemo.html
 * 
 * TODO: broadcast receivers should be configured in the AndroidManifest, to
 * remove the need to dynamically register / unregister
 * 
 * @author mfaulk
 * 
 */
public class ShakeAlertActivity extends MapActivity {
	@SuppressWarnings("unused")
	private static final String TAG = "ShakeAlert";
	private static final int WAVEFORMS_ID = Menu.FIRST;
	private static final int INFO_ID = Menu.FIRST + 1;
	private static final int QUIT_ID = Menu.FIRST + 2;
	private static final int SIMULATION = Menu.FIRST + 3;


	private MapView mapView;
	private MapController mapController;
	private MyLocationOverlay myLocationOverlay;
	private Cursor earthquakeCursor;

	private EarthquakeReceiver earthquakeReceiver;
	private PickBroadcastReceiver pickBroadcastReceiver;
	
	private AlertOverlay Alert;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Run the C2DM setup first if necessary
		SharedPreferences prefs = Prefs.get(this);
		if (prefs.getString("deviceRegistrationID", null) == null) {
			startActivity(new Intent(this, SetupActivity.class));
		}

		setContentView(R.layout.shake_map2);

		mapView = (MapView) findViewById(R.id.mapView);
		mapView.setBuiltInZoomControls(true);
		mapView.setSatellite(true);

		// set default zoom level

		mapController = mapView.getController();

		// ----- USGS quakes ----- //
		refreshEarthquakes();

		Uri earthquakeURI = EarthquakeProvider.CONTENT_URI;
		earthquakeCursor = getContentResolver().query(earthquakeURI, null,
				null, null, null);
		// let Android manage this cursor for us
		startManagingCursor(earthquakeCursor);
		Drawable quakeMarker = getResources().getDrawable(R.drawable.reddot);
		quakeMarker.setBounds(-quakeMarker.getIntrinsicWidth() / 2, quakeMarker.getIntrinsicHeight() / 2,
							  quakeMarker.getIntrinsicWidth() / 2, -quakeMarker.getIntrinsicHeight() / 2);
		EarthquakeItemizedOverlay eo = new EarthquakeItemizedOverlay(
				quakeMarker, earthquakeCursor, getApplicationContext());
		mapView.getOverlays().add(eo);

		// ----- client's location ----- //
		myLocationOverlay = new MyLocationOverlay(this, mapView);
		// add client's position
		// animate to client's location
		myLocationOverlay.enableMyLocation();
		
		// toggle if want to enable zooming on first fix
		/*
			myLocationOverlay.runOnFirstFix(new Runnable() {
				public void run() {
					mapController.animateTo(myLocationOverlay.getMyLocation());
					mapController.setZoom(10);
				}
			});
		*/
		
		
		mapView.getOverlays().add(myLocationOverlay);

		mapView.postInvalidate();

		// start main service
		Intent myIntent = new Intent(ShakeAlertActivity.this,
				ShakeAlertService.class);
		startService(myIntent);

		// ---- register for pick broadcasts ---- //
		IntentFilter filter = new IntentFilter(ShakeAlertService.PICK);
		pickBroadcastReceiver = new PickBroadcastReceiver();
		registerReceiver(pickBroadcastReceiver, filter);
		
		// --- register for earthquake broadcasts --- //
		
		IntentFilter earthquakeFilter;
		earthquakeFilter = new IntentFilter(EarthquakeService.NEW_EARTHQUAKE_FOUND);
		earthquakeReceiver = new EarthquakeReceiver();
		registerReceiver(earthquakeReceiver, earthquakeFilter);
		
		// --- request focus to handle events --- //
		mapView.setFocusableInTouchMode(true);
		mapView.requestFocus();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		boolean retVal = super.onCreateOptionsMenu(menu);

		// TODO: this should inflate an XML layout in res/menu
		menu.add(0, ShakeAlertActivity.WAVEFORMS_ID, 0, "Accelerometer");
		menu.add(0, ShakeAlertActivity.INFO_ID, 0, "About");
		menu.add(0, ShakeAlertActivity.QUIT_ID, 0, "Quit");
		menu.add(0, ShakeAlertActivity.SIMULATION, 0, "Simulation");

		return retVal;
	}

	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		switch (item.getItemId()) {
		case INFO_ID:
			// Intent infoIntent = new Intent(ShakeAlertActivity.this,
			// ClientInfo.class);
			// startActivity(infoIntent);
			// finish();
			startActivity(new Intent(this, HelpActivity.class));
			break;
		case WAVEFORMS_ID:
			Intent waveformIntent = new Intent(ShakeAlertActivity.this,
					WaveformDisplay.class);
			startActivity(waveformIntent);
			finish();
			break;
		case QUIT_ID:
			stopService(new Intent(ShakeAlertActivity.this,
					ShakeAlertService.class));
			stopService(new Intent(ShakeAlertActivity.this,
					EarthquakeService.class));
			finish();
			break;
		// some testcode to be deleted
		case SIMULATION:
			Intent intent = new Intent(EarthquakeService.NEW_EARTHQUAKE_FOUND);
			intent.putExtra("longitude", -120.0);
			intent.putExtra("latitude", 35.0);
			intent.putExtra("magnitude", 6.9f);
			intent.putExtra("date", System.currentTimeMillis());
			sendBroadcast(intent);
			break;
		default:
			//Log.d(TAG, "ShakeAlert:invalid menu item ID");
			break;
		}

		return super.onMenuItemSelected(featureId, item);
	}

	@Override
	public void onPause() {
		myLocationOverlay.disableMyLocation();
		earthquakeCursor.deactivate();
		unregisterReceiver(earthquakeReceiver);
		unregisterReceiver(pickBroadcastReceiver);
		super.onPause();
	}

	@Override
	public void onResume() {
		super.onResume();
		myLocationOverlay.enableMyLocation();
		earthquakeCursor.requery();
		IntentFilter filter;
		filter = new IntentFilter(EarthquakeService.NEW_EARTHQUAKE_FOUND);
		earthquakeReceiver = new EarthquakeReceiver();
		registerReceiver(earthquakeReceiver, filter);

		// ---- register for pick broadcasts ---- //
		IntentFilter pickFilter = new IntentFilter(ShakeAlertService.PICK);
		pickBroadcastReceiver = new PickBroadcastReceiver();
		registerReceiver(pickBroadcastReceiver, pickFilter);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		// TODO: stop the EarthquakeService?
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.android.maps.MapActivity#isRouteDisplayed()
	 */
	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	private void refreshEarthquakes() {
		startService(new Intent(this, EarthquakeService.class));
	}

	/**
	 * receive new earthquake broadcasts
	 */
	private class PickBroadcastReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {

			if (intent.getAction().equals(ShakeAlertService.PICK)) {

				Pick p = intent.getParcelableExtra(ShakeAlertService.PICK_KEY);

				// only display toast if this activity is visible
				// hasWindowFocus sort of checks if this window is visible
				if (p != null && hasWindowFocus()) {
					Toast toast = Toast.makeText(getApplicationContext(),
							"Pick", Toast.LENGTH_SHORT);
					toast.show();
				}

			}
		}
	}
	
	/*
	 * Receiver of earthquake broadcasts
	 */
	public class EarthquakeReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			earthquakeCursor.requery();
			MapView earthquakeMap = (MapView) findViewById(R.id.mapView);

			
			// get data from the intent of earthquake broadcast
			Uri data = intent.getData();
			long latitude = Math.round(intent.getDoubleExtra("latitude", 0.0) * 1E6);
			long longitude = Math.round(intent.getDoubleExtra("longitude", 0.0) * 1E6);
			long date = intent.getLongExtra("date", 0);
			float magnitude = intent.getFloatExtra("magnitude", 0.0f);

	        // our location
			int myLatitude = myLocationOverlay.getMyLocation().getLatitudeE6();
			int myLongitude = myLocationOverlay.getMyLocation().getLongitudeE6();
			
			// check max min boundaries for quake latitudes/longitudes
			latitude = Math.min(Integer.MAX_VALUE, latitude);
			latitude = Math.max(Integer.MIN_VALUE, latitude);
			longitude = Math.min(Integer.MAX_VALUE, longitude);
			longitude = Math.max(Integer.MIN_VALUE, longitude);

			
	        // the geographic location of the earthquake
	        GeoPoint quakeLoc = new GeoPoint(
	            (int) latitude, 
	            (int) longitude);
			
			// if the earthquake is a threat, animate to it
			// TODO: add some diagnosis to see if the earthquake is a threat to my location
			// TODO: tidy it up when it will be possible to access the latest earthquake from the itemized overlay
			//		 to add the item to the list of itemized overlays
			boolean isThreat = true;
			
			// placeholder for estimating threat
			if (magnitude < 4.0f)
				isThreat = false;
			
			if (isThreat) {
				// lock the screen orientation
				// TODO: reorganize the code so that screen orientation lock is not required.
				setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
				GeoPoint middlePoint = new GeoPoint(
			            (int) (quakeLoc.getLatitudeE6() + myLatitude) / 2,
			            (int) (quakeLoc.getLongitudeE6() + myLongitude) / 2);
		        
				QuakeOverlayItem newQuake = new QuakeOverlayItem(quakeLoc, "title", "snippet", magnitude, date);  
	            List<Overlay> listOfOverlays = mapView.getOverlays();
	            
	            if (Alert != null) 
	            	listOfOverlays.remove(Alert);
	            
	            Alert = new AlertOverlay(newQuake);
	            listOfOverlays.add(Alert);
				
		        // zoom into the scene
		        mapView.getController().animateTo(middlePoint);
				
		        // zoom so that we see both the earthquake and our location
				mapView.getController().zoomToSpan((int) Math.abs(myLatitude - quakeLoc.getLatitudeE6()),
												  (int) Math.abs(myLongitude - quakeLoc.getLongitudeE6()));
				// Alert the user with a a toast
				Toast toast = Toast.makeText(getApplicationContext(),
						"Earthquake detected! Press center on the d-pad to stop.", Toast.LENGTH_LONG);
				toast.show();
				earthquakeMap.invalidate();
			}
		}
	}   	
    }

	/*
	 * Helper overlay for displaying a new earthquake alert from the network
	 */

    class AlertOverlay extends Overlay
    {
      	private long animTime;
      	private QuakeOverlayItem quakeLocation;
      	
      	/*
      	 * Class constructor that intializes earthquake data.
      	 */
      	public AlertOverlay(QuakeOverlayItem quake){
      		quakeLocation = quake;
      		animTime = -1;
      	}
      	
      	/*
      	 * (non-Javadoc)
      	 * @see com.google.android.maps.Overlay#draw(android.graphics.Canvas, com.google.android.maps.MapView, boolean, long)
      	 * Draw method for the alert overlay.
      	 */
    	
        @Override
        public boolean draw(Canvas canvas, MapView mapView, 
        boolean shadow, long when) 
        {
            super.draw(canvas, mapView, shadow);                   
            // set the inner color
            
            // if exists, draw
            if (quakeLocation != null) {
            	
            	GeoPoint p = quakeLocation.getPoint();
            	
    	        //---translate the GeoPoint to screen pixels---
    	        Point screenPts = new Point();
    	        mapView.getProjection().toPixels(p, screenPts);
    	        
    	        // TODO: put colors as resources elsewhere to tidy code
    	        // set the inner color
    	        Paint color1 = new Paint();
    	        color1.setAntiAlias(true);
    	        color1.setColor(Color.RED);
    	        color1.setStyle(Paint.Style.STROKE);
    	        
    	        // set the outer color
    	        Paint color2 = new Paint();
    	        color2.setAntiAlias(true);
    	        color2.setColor(0x55EEEEEE);
    	        color2.setStyle(Paint.Style.FILL);
    	        
    	        //---add the range circle ---
    	        float radius = quakeLocation.getRadius();

    	        // adjust radius to scale
    	        // in cylindrical projection (such as Mercator) the radius is scaled by the secant of the longitude
    	        float pixelRadius  = mapView.getProjection().metersToEquatorPixels(radius);
    	        pixelRadius /= (float) Math.cos(Math.toRadians(p.getLatitudeE6()  * 1E6));
    	
    	        
    	        // draw outer circle
    	        canvas.drawCircle(screenPts.x, screenPts.y, pixelRadius, color1);
    	        
    	        // draw filling
    	        canvas.drawCircle(screenPts.x, screenPts.y, pixelRadius, color2);
    	        
    	        
    	        // TODO: animation part
    	        // TODO: make wave radius dependent on the start time
    	        // TODO: make the period of generating waves more realistic
    	        
    	        Paint color3 = new Paint();
    	        color3.setAntiAlias(true);
    	        color3.setColor(Color.BLUE);
    	        color3.setStyle(Paint.Style.STROKE);
    	        color3.setAlpha(128);
    	        color3.setStrokeWidth(3);
    	        
    	        // calculate the number of waves that we can show on screen
    	        int iterations = Math.round(
    	        		(float) Math.floor(radius / (quakeLocation.getSpeed() * quakeLocation.getPeriod())));
    	        // calculate the total number of waves the earthquake has created
    	        if (animTime == -1)
    	        	animTime = when;
    	        float totalIterations = (float) Math.floor((quakeLocation.getTapTime() + (when - animTime) / 1000) /
    	        								quakeLocation.getPeriod());
    	        
    	        // adjustment factor to calculate the current position of the wave
    	        float fract = (float) (quakeLocation.getAge() / quakeLocation.getPeriod() - totalIterations);
    	        
    	        float waveRadius = (float) (fract * quakeLocation.getSpeed() * quakeLocation.getPeriod());
    	        float pixelWaveRadius = 0;
    	        
    	        
    	        // draw the circles
    	        for (int i = 0; i <= iterations; i++) {
    	        	pixelWaveRadius = mapView.getProjection().metersToEquatorPixels(waveRadius);
    	        	pixelWaveRadius /= (float) Math.cos(Math.toRadians(p.getLatitudeE6()  * 1E6));
    		        if (pixelWaveRadius > pixelRadius)
    		        	break;
    		        canvas.drawCircle(screenPts.x, screenPts.y, pixelWaveRadius, color3);
    		        waveRadius += quakeLocation.getSpeed() * quakeLocation.getPeriod();
    	        }
    	    }
            
            return true;
        }
        
        /*
         * (non-Javadoc)
         * @see com.google.android.maps.Overlay#onKeyDown(int, android.view.KeyEvent, com.google.android.maps.MapView)
         * Helper testing method to cancel the drawing of the alert circle.
         */
        
        @Override
        public boolean onKeyDown(int keyCode, android.view.KeyEvent event, MapView mapView) {
        	super.onKeyDown(keyCode, event, mapView);
        	if (keyCode == android.view.KeyEvent.KEYCODE_DPAD_CENTER) {
	        	List<Overlay> listOfOverlays = mapView.getOverlays();
	        	listOfOverlays.remove(this);
	        	mapView.invalidate();
	        	return true;
	        	}
        	return false;
        }
	
	
} // end of class

